#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <time.h>
#ifdef LINUX_HOST
#include <bpf/bpf.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>
#else
#include "bpf.h"
#include "btf.h"
#include "libbpf.h"
#endif // LINUX_HOST
#include <limits.h>
#include "trace_helpers.h"
#include "uprobe_helpers.h"

// #if defined(__GNUC__)
// #define min(x, y) ({				\
// 	typeof(x) _min1 = (x);			\
// 	typeof(y) _min2 = (y);			\
// 	(void) (&_min1 == &_min2);		\
// 	_min1 < _min2 ? _min1 : _min2; })
// #elif defined(__clang__)
#define min(x, y) ({				\
    __typeof__(x) _min1 = (x);	 	\
    __typeof__(y) _min2 = (y);		\
    (void) (&_min1 == &_min2);		\
    _min1 < _min2 ? _min1 : _min2; })
// #endif

#define DISK_NAME_LEN	32

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MKDEV(ma, mi)	(((ma) << MINORBITS) | (mi))

struct ksyms {
    struct ksym *syms;
    int syms_sz;
    int syms_cap;
    char *strs;
    int strs_sz;
    int strs_cap;
};

static int ksyms__add_symbol(struct ksyms *ksyms, const char *name, unsigned long addr)
{
    size_t new_cap, name_len = strlen(name) + 1;
    struct ksym *ksym;
    void *tmp;

    if (ksyms->strs_sz + name_len > ksyms->strs_cap) {
        new_cap = ksyms->strs_cap * 4 / 3;
        if (new_cap < ksyms->strs_sz + name_len)
            new_cap = ksyms->strs_sz + name_len;
        if (new_cap < 1024)
            new_cap = 1024;
        tmp = realloc(ksyms->strs, new_cap);
        if (!tmp)
            return -1;
        ksyms->strs = tmp;
        ksyms->strs_cap = new_cap;
    }
    if (ksyms->syms_sz + 1 > ksyms->syms_cap) {
        new_cap = ksyms->syms_cap * 4 / 3;
        if (new_cap < 1024)
            new_cap = 1024;
        tmp = realloc(ksyms->syms, sizeof(*ksyms->syms) * new_cap);
        if (!tmp)
            return -1;
        ksyms->syms = tmp;
        ksyms->syms_cap = new_cap;
    }

    ksym = &ksyms->syms[ksyms->syms_sz];
    /* while constructing, re-use pointer as just a plain offset */
    ksym->name = (void *)(unsigned long)ksyms->strs_sz;
    ksym->addr = addr;

    memcpy(ksyms->strs + ksyms->strs_sz, name, name_len);
    ksyms->strs_sz += name_len;
    ksyms->syms_sz++;

    return 0;
}

static int ksym_cmp(const void *p1, const void *p2)
{
    const struct ksym *s1 = p1, *s2 = p2;

    if (s1->addr == s2->addr)
        return strcmp(s1->name, s2->name);
    return s1->addr < s2->addr ? -1 : 1;
}

struct ksyms *ksyms__load(void)
{
    char sym_type, sym_name[256];
    struct ksyms *ksyms;
    unsigned long sym_addr;
    int i, ret;
    FILE *f;

    f = fopen("/proc/kallsyms", "r");
    if (!f)
        return NULL;

    ksyms = calloc(1, sizeof(*ksyms));
    if (!ksyms)
        goto err_out;

    while (true) {
        ret = fscanf(f, "%lx %c %s%*[^\n]\n",
                 &sym_addr, &sym_type, sym_name);
        if (ret == EOF && feof(f))
            break;
        if (ret != 3)
            goto err_out;
        if (ksyms__add_symbol(ksyms, sym_name, sym_addr))
            goto err_out;
    }

    /* now when strings are finalized, adjust pointers properly */
    for (i = 0; i < ksyms->syms_sz; i++)
        ksyms->syms[i].name += (unsigned long)ksyms->strs;

    qsort(ksyms->syms, ksyms->syms_sz, sizeof(*ksyms->syms), ksym_cmp);

    fclose(f);
    return ksyms;

err_out:
    ksyms__free(ksyms);
    fclose(f);
    return NULL;
}

void ksyms__free(struct ksyms *ksyms)
{
    if (!ksyms)
        return;

    free(ksyms->syms);
    free(ksyms->strs);
    free(ksyms);
}

const struct ksym *ksyms__map_addr(const struct ksyms *ksyms,
                   unsigned long addr)
{
    int start = 0, end = ksyms->syms_sz - 1, mid;
    unsigned long sym_addr;

    /* find largest sym_addr <= addr using binary search */
    while (start < end) {
        mid = start + (end - start + 1) / 2;
        sym_addr = ksyms->syms[mid].addr;

        if (sym_addr <= addr)
            start = mid;
        else
            end = mid - 1;
    }

    if (start == end && ksyms->syms[start].addr <= addr)
        return &ksyms->syms[start];
    return NULL;
}

const struct ksym *ksyms__get_symbol(const struct ksyms *ksyms,
                     const char *name)
{
    int i;

    for (i = 0; i < ksyms->syms_sz; i++) {
        if (strcmp(ksyms->syms[i].name, name) == 0)
            return &ksyms->syms[i];
    }

    return NULL;
}

struct load_range {
    uint64_t start;
    uint64_t end;
    uint64_t file_off;
};

enum elf_type {
    EXEC,
    DYN,
    PERF_MAP,
    VDSO,
    UNKNOWN,
};

struct dso {
    char *name;
    struct load_range *ranges;
    int range_sz;
    /* Dyn's first text section virtual addr at execution */
    uint64_t sh_addr;
    /* Dyn's first text section file offset */
    uint64_t sh_offset;
    enum elf_type type;

    struct sym *syms;
    int syms_sz;
    int syms_cap;

    /*
     * libbpf's struct btf is actually a pretty efficient
     * "set of strings" data structure, so we create an
     * empty one and use it to store symbol names.
     */
    struct btf *btf;
};

struct map {
    uint64_t start_addr;
    uint64_t end_addr;
    uint64_t file_off;
    uint64_t dev_major;
    uint64_t dev_minor;
    uint64_t inode;
};

struct syms {
    struct dso *dsos;
    int dso_sz;
};

static bool is_file_backed(const char *mapname)
{
#define STARTS_WITH(mapname, prefix) \
    (!strncmp(mapname, prefix, sizeof(prefix) - 1))

    return mapname[0] && !(
        STARTS_WITH(mapname, "//anon") ||
        STARTS_WITH(mapname, "/dev/zero") ||
        STARTS_WITH(mapname, "/anon_hugepage") ||
        STARTS_WITH(mapname, "[stack") ||
        STARTS_WITH(mapname, "/SYSV") ||
        STARTS_WITH(mapname, "[heap]") ||
        STARTS_WITH(mapname, "[uprobes]") ||
        STARTS_WITH(mapname, "[vsyscall]"));
}

static bool is_perf_map(const char *path)
{
    return false;
}

static bool is_vdso(const char *path)
{
    return !strcmp(path, "[vdso]");
}

static bool is_uprobes(const char *path)
{
    return !strcmp(path, "[uprobes]");
}

static int get_elf_type(const char *path)
{
    GElf_Ehdr hdr;
    void *res;
    Elf *e;
    int fd;

    if (is_vdso(path))
        return -1;
    if (is_uprobes(path))
        return -1;
    e = open_elf(path, &fd);
    if (!e)
        return -1;
    res = gelf_getehdr(e, &hdr);
    close_elf(e, fd);
    if (!res)
        return -1;
    return hdr.e_type;
}

static int get_elf_text_scn_info(const char *path, uint64_t *addr,
                 uint64_t *offset)
{
    Elf_Scn *section = NULL;
    int fd = -1, err = -1;
    GElf_Shdr header;
    size_t stridx;
    Elf *e = NULL;
    char *name;

    e = open_elf(path, &fd);
    if (!e)
        goto err_out;
    err = elf_getshdrstrndx(e, &stridx);
    if (err < 0)
        goto err_out;

    err = -1;
    while ((section = elf_nextscn(e, section)) != 0) {
        if (!gelf_getshdr(section, &header))
            continue;

        name = elf_strptr(e, stridx, header.sh_name);
        if (name && !strcmp(name, ".text")) {
            *addr = (uint64_t)header.sh_addr;
            *offset = (uint64_t)header.sh_offset;
            err = 0;
            break;
        }
    }

err_out:
    close_elf(e, fd);
    return err;
}

static int syms__add_dso(struct syms *syms, struct map *map, const char *name)
{
    struct dso *dso = NULL;
    int i, type;
    void *tmp;

    for (i = 0; i < syms->dso_sz; i++) {
        if (!strcmp(syms->dsos[i].name, name)) {
            dso = &syms->dsos[i];
            break;
        }
    }

    if (!dso) {
        tmp = realloc(syms->dsos, (syms->dso_sz + 1) *
                  sizeof(*syms->dsos));
        if (!tmp)
            return -1;
        syms->dsos = tmp;
        dso = &syms->dsos[syms->dso_sz++];
        memset(dso, 0, sizeof(*dso));
        dso->name = strdup(name);
        dso->btf = btf__new_empty();
    }

    tmp = realloc(dso->ranges, (dso->range_sz + 1) * sizeof(*dso->ranges));
    if (!tmp)
        return -1;
    dso->ranges = tmp;
    dso->ranges[dso->range_sz].start = map->start_addr;
    dso->ranges[dso->range_sz].end = map->end_addr;
    dso->ranges[dso->range_sz].file_off = map->file_off;
    dso->range_sz++;
    type = get_elf_type(name);
    if (type == ET_EXEC) {
        dso->type = EXEC;
    } else if (type == ET_DYN) {
        dso->type = DYN;
        if (get_elf_text_scn_info(name, &dso->sh_addr, &dso->sh_offset) < 0)
            return -1;
    } else if (is_perf_map(name)) {
        dso->type = PERF_MAP;
    } else if (is_vdso(name)) {
        dso->type = VDSO;
    } else {
        dso->type = UNKNOWN;
    }
    return 0;
}

static struct dso *syms__find_dso(const struct syms *syms, unsigned long addr,
                  uint64_t *offset)
{
    struct load_range *range;
    struct dso *dso;
    int i, j;

    for (i = 0; i < syms->dso_sz; i++) {
        dso = &syms->dsos[i];
        for (j = 0; j < dso->range_sz; j++) {
            range = &dso->ranges[j];
            if (addr <= range->start || addr >= range->end)
                continue;
            if (dso->type == DYN || dso->type == VDSO) {
                /* Offset within the mmap */
                *offset = addr - range->start + range->file_off;
                /* Offset within the ELF for dyn symbol lookup */
                *offset += dso->sh_addr - dso->sh_offset;
            } else {
                *offset = addr;
            }

            return dso;
        }
    }

    return NULL;
}

static int dso__load_sym_table_from_perf_map(struct dso *dso)
{
    return -1;
}

static int dso__add_sym(struct dso *dso, const char *name, uint64_t start,
            uint64_t size)
{
    struct sym *sym;
    size_t new_cap;
    void *tmp;
    int off;

    off = btf__add_str(dso->btf, name);
    if (off < 0)
        return off;

    if (dso->syms_sz + 1 > dso->syms_cap) {
        new_cap = dso->syms_cap * 4 / 3;
        if (new_cap < 1024)
            new_cap = 1024;
        tmp = realloc(dso->syms, sizeof(*dso->syms) * new_cap);
        if (!tmp)
            return -1;
        dso->syms = tmp;
        dso->syms_cap = new_cap;
    }

    sym = &dso->syms[dso->syms_sz++];
    /* while constructing, re-use pointer as just a plain offset */
    sym->name = (void*)(unsigned long)off;
    sym->start = start;
    sym->size = size;
    sym->offset = 0;

    return 0;
}

static int sym_cmp(const void *p1, const void *p2)
{
    const struct sym *s1 = p1, *s2 = p2;

    if (s1->start == s2->start)
        return strcmp(s1->name, s2->name);
    return s1->start < s2->start ? -1 : 1;
}

static int dso__add_syms(struct dso *dso, Elf *e, Elf_Scn *section,
             size_t stridx, size_t symsize)
{
    Elf_Data *data = NULL;

    while ((data = elf_getdata(section, data)) != 0) {
        size_t i, symcount = data->d_size / symsize;

        if (data->d_size % symsize)
            return -1;

        for (i = 0; i < symcount; ++i) {
            const char *name;
            GElf_Sym sym;

            if (!gelf_getsym(data, (int)i, &sym))
                continue;
            if (!(name = elf_strptr(e, stridx, sym.st_name)))
                continue;
            if (name[0] == '\0')
                continue;

            if (sym.st_value == 0)
                continue;

            if (dso__add_sym(dso, name, sym.st_value, sym.st_size))
                goto err_out;
        }
    }

    return 0;

err_out:
    return -1;
}

static void dso__free_fields(struct dso *dso)
{
    if (!dso)
        return;

    free(dso->name);
    free(dso->ranges);
    free(dso->syms);
    btf__free(dso->btf);
}

static int dso__load_sym_table_from_elf(struct dso *dso, int fd)
{
    Elf_Scn *section = NULL;
    Elf *e;
    int i;

    e = fd > 0 ? open_elf_by_fd(fd) : open_elf(dso->name, &fd);
    if (!e)
        return -1;

    while ((section = elf_nextscn(e, section)) != 0) {
        GElf_Shdr header;

        if (!gelf_getshdr(section, &header))
            continue;

        if (header.sh_type != SHT_SYMTAB &&
            header.sh_type != SHT_DYNSYM)
            continue;

        if (dso__add_syms(dso, e, section, header.sh_link,
                  header.sh_entsize))
            goto err_out;
    }

    /* now when strings are finalized, adjust pointers properly */
    for (i = 0; i < dso->syms_sz; i++)
        dso->syms[i].name =
            btf__name_by_offset(dso->btf,
                        (unsigned long)dso->syms[i].name);

    qsort(dso->syms, dso->syms_sz, sizeof(*dso->syms), sym_cmp);

    close_elf(e, fd);
    return 0;

err_out:
    dso__free_fields(dso);
    close_elf(e, fd);
    return -1;
}

static int create_tmp_vdso_image(struct dso *dso)
{
    uint64_t start_addr, end_addr;
    long pid = getpid();
    char buf[PATH_MAX];
    void *image = NULL;
    char tmpfile[128];
    int ret, fd = -1;
    uint64_t sz;
    char *name;
    FILE *f;

    snprintf(tmpfile, sizeof(tmpfile), "/proc/%ld/maps", pid);
    f = fopen(tmpfile, "r");
    if (!f)
        return -1;

    while (true) {
        ret = fscanf(f, "%llx-%llx %*s %*x %*x:%*x %*u%[^\n]",
                 (long long*)&start_addr, (long long*)&end_addr,
                 buf);
        if (ret == EOF && feof(f))
            break;
        if (ret != 3)
            goto err_out;

        name = buf;
        while (isspace(*name))
            name++;
        if (!is_file_backed(name))
            continue;
        if (is_vdso(name))
            break;
    }

    sz = end_addr - start_addr;
    image = malloc(sz);
    if (!image)
        goto err_out;
    memcpy(image, (void *)start_addr, sz);

    snprintf(tmpfile, sizeof(tmpfile),
         "/tmp/libbpf_%ld_vdso_image_XXXXXX", pid);
    fd = mkostemp(tmpfile, O_CLOEXEC);
    if (fd < 0) {
        fprintf(stderr, "failed to create temp file: %s\n",
            strerror(errno));
        goto err_out;
    }
    /* Unlink the file to avoid leaking */
    if (unlink(tmpfile) == -1)
        fprintf(stderr, "failed to unlink %s: %s\n", tmpfile,
            strerror(errno));
    if (write(fd, image, sz) == -1) {
        fprintf(stderr, "failed to write to vDSO image: %s\n",
            strerror(errno));
        close(fd);
        fd = -1;
        goto err_out;
    }

err_out:
    fclose(f);
    free(image);
    return fd;
}

static int dso__load_sym_table_from_vdso_image(struct dso *dso)
{
    int fd = create_tmp_vdso_image(dso);

    if (fd < 0)
        return -1;
    return dso__load_sym_table_from_elf(dso, fd);
}

static int dso__load_sym_table(struct dso *dso)
{
    if (dso->type == UNKNOWN)
        return -1;
    if (dso->type == PERF_MAP)
        return dso__load_sym_table_from_perf_map(dso);
    if (dso->type == EXEC || dso->type == DYN)
        return dso__load_sym_table_from_elf(dso, 0);
    if (dso->type == VDSO)
        return dso__load_sym_table_from_vdso_image(dso);
    return -1;
}

static struct sym *dso__find_sym(struct dso *dso, uint64_t offset)
{
    unsigned long sym_addr;
    int start, end, mid;

    if (!dso->syms && dso__load_sym_table(dso))
        return NULL;

    start = 0;
    end = dso->syms_sz - 1;

    /* find largest sym_addr <= addr using binary search */
    while (start < end) {
        mid = start + (end - start + 1) / 2;
        sym_addr = dso->syms[mid].start;

        if (sym_addr <= offset)
            start = mid;
        else
            end = mid - 1;
    }

    if (start == end && dso->syms[start].start <= offset &&
        offset < dso->syms[start].start + dso->syms[start].size) {
        (dso->syms[start]).offset = offset - dso->syms[start].start;
        return &dso->syms[start];
    }
    return NULL;
}

struct syms *syms__load_file(const char *fname)
{
    char buf[PATH_MAX], perm[5];
    struct syms *syms;
    struct map map;
    char *name;
    FILE *f;
    int ret;

    f = fopen(fname, "r");
    if (!f)
        return NULL;

    syms = calloc(1, sizeof(*syms));
    if (!syms)
        goto err_out;

    while (true) {
        ret = fscanf(f, "%llx-%llx %4s %llx %llx:%llx %llu%[^\n]",
                 (long long*)&map.start_addr,
                 (long long*)&map.end_addr, perm,
                 (long long*)&map.file_off,
                 (long long*)&map.dev_major,
                 (long long*)&map.dev_minor,
                 (long long*)&map.inode, buf);
        if (ret == EOF && feof(f))
            break;
        if (ret != 8)	/* perf-<PID>.map */
            goto err_out;

        if (perm[2] != 'x')
            continue;

        name = buf;
        while (isspace(*name))
            name++;
        if (!is_file_backed(name))
            continue;

        if (syms__add_dso(syms, &map, name))
            goto err_out;
    }

    fclose(f);
    return syms;

err_out:
    syms__free(syms);
    fclose(f);
    return NULL;
}

struct syms *syms__load_pid(pid_t tgid)
{
    char fname[128];

    snprintf(fname, sizeof(fname), "/proc/%ld/maps", (long)tgid);
    return syms__load_file(fname);
}

void syms__free(struct syms *syms)
{
    int i;

    if (!syms)
        return;

    for (i = 0; i < syms->dso_sz; i++)
        dso__free_fields(&syms->dsos[i]);
    free(syms->dsos);
    free(syms);
}

const struct sym *syms__map_addr(const struct syms *syms, unsigned long addr)
{
    struct dso *dso;
    uint64_t offset;

    dso = syms__find_dso(syms, addr, &offset);
    if (!dso)
        return NULL;
    return dso__find_sym(dso, offset);
}

int syms__map_addr_dso(const struct syms *syms, unsigned long addr,
               struct sym_info *sinfo)
{
    struct dso *dso;
    struct sym *sym;
    uint64_t offset;

    memset(sinfo, 0x0, sizeof(struct sym_info));

    dso = syms__find_dso(syms, addr, &offset);
    if (!dso)
        return -1;

    sinfo->dso_name = dso->name;
    sinfo->dso_offset = offset;

    sym = dso__find_sym(dso, offset);
    if (sym) {
        sinfo->sym_name = sym->name;
        sinfo->sym_offset = sym->offset;
    }

    return 0;
}

struct syms_cache {
    struct {
        struct syms *syms;
        int tgid;
    } *data;
    int nr;
};

struct syms_cache *syms_cache__new(int nr)
{
    struct syms_cache *syms_cache;

    syms_cache = calloc(1, sizeof(*syms_cache));
    if (!syms_cache)
        return NULL;
    if (nr > 0)
        syms_cache->data = calloc(nr, sizeof(*syms_cache->data));
    return syms_cache;
}

void syms_cache__free(struct syms_cache *syms_cache)
{
    int i;

    if (!syms_cache)
        return;

    for (i = 0; i < syms_cache->nr; i++)
        syms__free(syms_cache->data[i].syms);
    free(syms_cache->data);
    free(syms_cache);
}

struct syms *syms_cache__get_syms(struct syms_cache *syms_cache, int tgid)
{
    void *tmp;
    int i;

    for (i = 0; i < syms_cache->nr; i++) {
        if (syms_cache->data[i].tgid == tgid)
            return syms_cache->data[i].syms;
    }

    tmp = realloc(syms_cache->data, (syms_cache->nr + 1) *
              sizeof(*syms_cache->data));
    if (!tmp)
        return NULL;
    syms_cache->data = tmp;
    syms_cache->data[syms_cache->nr].syms = syms__load_pid(tgid);
    syms_cache->data[syms_cache->nr].tgid = tgid;
    return syms_cache->data[syms_cache->nr++].syms;
}

struct partitions {
    struct partition *items;
    int sz;
};

static int partitions__add_partition(struct partitions *partitions,
                     const char *name, unsigned int dev)
{
    struct partition *partition;
    void *tmp;

    tmp = realloc(partitions->items, (partitions->sz + 1) *
        sizeof(*partitions->items));
    if (!tmp)
        return -1;
    partitions->items = tmp;
    partition = &partitions->items[partitions->sz];
    partition->name = strdup(name);
    partition->dev = dev;
    partitions->sz++;

    return 0;
}

struct partitions *partitions__load(void)
{
    char part_name[DISK_NAME_LEN];
    unsigned int devmaj, devmin;
    unsigned long long nop;
    struct partitions *partitions;
    char buf[64];
    FILE *f;

    f = fopen("/proc/partitions", "r");
    if (!f)
        return NULL;

    partitions = calloc(1, sizeof(*partitions));
    if (!partitions)
        goto err_out;

    while (fgets(buf, sizeof(buf), f) != NULL) {
        /* skip heading */
        if (buf[0] != ' ' || buf[0] == '\n')
            continue;
        if (sscanf(buf, "%u %u %llu %s", &devmaj, &devmin, &nop,
                part_name) != 4)
            goto err_out;
        if (partitions__add_partition(partitions, part_name,
                        MKDEV(devmaj, devmin)))
            goto err_out;
    }

    fclose(f);
    return partitions;

err_out:
    partitions__free(partitions);
    fclose(f);
    return NULL;
}

void partitions__free(struct partitions *partitions)
{
    int i;

    if (!partitions)
        return;

    for (i = 0; i < partitions->sz; i++)
        free(partitions->items[i].name);
    free(partitions->items);
    free(partitions);
}

const struct partition *
partitions__get_by_dev(const struct partitions *partitions, unsigned int dev)
{
    int i;

    for (i = 0; i < partitions->sz; i++) {
        if (partitions->items[i].dev == dev)
            return &partitions->items[i];
    }

    return NULL;
}

const struct partition *
partitions__get_by_name(const struct partitions *partitions, const char *name)
{
    int i;

    for (i = 0; i < partitions->sz; i++) {
        if (strcmp(partitions->items[i].name, name) == 0)
            return &partitions->items[i];
    }

    return NULL;
}

static void print_stars(unsigned int val, unsigned int val_max, int width)
{
    int num_stars, num_spaces, i;
    bool need_plus;

    num_stars = min(val, val_max) * width / val_max;
    num_spaces = width - num_stars;
    need_plus = val > val_max;

    for (i = 0; i < num_stars; i++)
        printf("*");
    for (i = 0; i < num_spaces; i++)
        printf(" ");
    if (need_plus)
        printf("+");
}

void print_log2_hist(unsigned int *vals, int vals_size, const char *val_type)
{
    int stars_max = 40, idx_max = -1;
    unsigned int val, val_max = 0;
    unsigned long long low, high;
    int stars, width, i;

    for (i = 0; i < vals_size; i++) {
        val = vals[i];
        if (val > 0)
            idx_max = i;
        if (val > val_max)
            val_max = val;
    }

    if (idx_max < 0)
        return;

    printf("%*s%-*s : count    distribution\n", idx_max <= 32 ? 5 : 15, "",
        idx_max <= 32 ? 19 : 29, val_type);

    if (idx_max <= 32)
        stars = stars_max;
    else
        stars = stars_max / 2;

    for (i = 0; i <= idx_max; i++) {
        low = (1ULL << (i + 1)) >> 1;
        high = (1ULL << (i + 1)) - 1;
        if (low == high)
            low -= 1;
        val = vals[i];
        width = idx_max <= 32 ? 10 : 20;
        printf("%*lld -> %-*lld : %-8d |", width, low, width, high, val);
        print_stars(val, val_max, stars);
        printf("|\n");
    }
}

void print_linear_hist(unsigned int *vals, int vals_size, unsigned int base,
               unsigned int step, const char *val_type)
{
    int i, stars_max = 40, idx_min = -1, idx_max = -1;
    unsigned int val, val_max = 0;

    for (i = 0; i < vals_size; i++) {
        val = vals[i];
        if (val > 0) {
            idx_max = i;
            if (idx_min < 0)
                idx_min = i;
        }
        if (val > val_max)
            val_max = val;
    }

    if (idx_max < 0)
        return;

    printf("     %-13s : count     distribution\n", val_type);
    for (i = idx_min; i <= idx_max; i++) {
        val = vals[i];
        if (!val)
            continue;
        printf("        %-10d : %-8d |", base + i * step, val);
        print_stars(val, val_max, stars_max);
        printf("|\n");
    }
}

unsigned long long get_ktime_ns(void)
{
    struct timespec ts;

    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec;
}

bool is_kernel_module(const char *name)
{
    bool found = false;
    char buf[64];
    FILE *f;

    f = fopen("/proc/modules", "r");
    if (!f)
        return false;

    while (fgets(buf, sizeof(buf), f) != NULL) {
        if (sscanf(buf, "%s %*s\n", buf) != 1)
            break;
        if (!strcmp(buf, name)) {
            found = true;
            break;
        }
    }

    fclose(f);
    return found;
}

static bool fentry_try_attach(int id)
{
    int prog_fd, attach_fd;
    char error[4096];
    struct bpf_insn insns[] = {
        { .code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = BPF_REG_0, .imm = 0 },
        { .code = BPF_JMP | BPF_EXIT },
    };
    LIBBPF_OPTS(bpf_prog_load_opts, opts,
            .expected_attach_type = BPF_TRACE_FENTRY,
            .attach_btf_id = id,
            .log_buf = error,
            .log_size = sizeof(error),
    );

    prog_fd = bpf_prog_load(BPF_PROG_TYPE_TRACING, "test", "GPL", insns,
            sizeof(insns) / sizeof(struct bpf_insn), &opts);
    if (prog_fd < 0)
        return false;

    attach_fd = bpf_raw_tracepoint_open(NULL, prog_fd);
    if (attach_fd >= 0)
        close(attach_fd);

    close(prog_fd);
    return attach_fd >= 0;
}

bool fentry_can_attach(const char *name, const char *mod)
{
    struct btf *btf, *vmlinux_btf, *module_btf = NULL;
    int err, id;

    vmlinux_btf = btf__load_vmlinux_btf();
    err = libbpf_get_error(vmlinux_btf);
    if (err)
        return false;

    btf = vmlinux_btf;

    if (mod) {
        module_btf = btf__load_module_btf(mod, vmlinux_btf);
        err = libbpf_get_error(module_btf);
        if (!err)
            btf = module_btf;
    }

    id = btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);

    btf__free(module_btf);
    btf__free(vmlinux_btf);
    return id > 0 && fentry_try_attach(id);
}

#define DEBUGFS "/sys/kernel/debug/tracing"
#define TRACEFS "/sys/kernel/tracing"

static bool use_debugfs(void)
{
    static int has_debugfs = -1;

    if (has_debugfs < 0)
        has_debugfs = faccessat(AT_FDCWD, DEBUGFS, F_OK, AT_EACCESS) == 0;

    return has_debugfs == 1;
}

static const char *tracefs_path(void)
{
    return use_debugfs() ? DEBUGFS : TRACEFS;
}

static const char *tracefs_available_filter_functions(void)
{
    return use_debugfs() ? DEBUGFS"/available_filter_functions" :
                   TRACEFS"/available_filter_functions";
}

bool kprobe_exists(const char *name)
{
    char addr_range[256];
    char sym_name[256];
    FILE *f;
    int ret;

    f = fopen("/sys/kernel/debug/kprobes/blacklist", "r");
    if (!f)
        goto avail_filter;

    while (true) {
        ret = fscanf(f, "%s %s%*[^\n]\n", addr_range, sym_name);
        if (ret == EOF && feof(f))
            break;
        if (ret != 2) {
            fprintf(stderr, "failed to read symbol from kprobe blacklist\n");
            break;
        }
        if (!strcmp(name, sym_name)) {
            fclose(f);
            return false;
        }
    }
    fclose(f);

avail_filter:
    f = fopen(tracefs_available_filter_functions(), "r");
    if (!f)
        goto slow_path;

    while (true) {
        ret = fscanf(f, "%s%*[^\n]\n", sym_name);
        if (ret == EOF && feof(f))
            break;
        if (ret != 1) {
            fprintf(stderr, "failed to read symbol from available_filter_functions\n");
            break;
        }
        if (!strcmp(name, sym_name)) {
            fclose(f);
            return true;
        }
    }

    fclose(f);
    return false;

slow_path:
    f = fopen("/proc/kallsyms", "r");
    if (!f)
        return false;

    while (true) {
        ret = fscanf(f, "%*x %*c %s%*[^\n]\n", sym_name);
        if (ret == EOF && feof(f))
            break;
        if (ret != 1) {
            fprintf(stderr, "failed to read symbol from kallsyms\n");
            break;
        }
        if (!strcmp(name, sym_name)) {
            fclose(f);
            return true;
        }
    }

    fclose(f);
    return false;
}

bool tracepoint_exists(const char *category, const char *event)
{
    char path[PATH_MAX];

    snprintf(path, sizeof(path), "%s/events/%s/%s/format", tracefs_path(), category, event);
    if (!access(path, F_OK))
        return true;
    return false;
}

bool vmlinux_btf_exists(void)
{
    struct btf *btf;
    int err;

    btf = btf__load_vmlinux_btf();
    err = libbpf_get_error(btf);
    if (err)
        return false;

    btf__free(btf);
    return true;
}

bool module_btf_exists(const char *mod)
{
    char sysfs_mod[80];

    if (mod) {
        snprintf(sysfs_mod, sizeof(sysfs_mod), "/sys/kernel/btf/%s", mod);
        if (!access(sysfs_mod, R_OK))
            return true;
    }
    return false;
}

bool probe_tp_btf(const char *name)
{
    LIBBPF_OPTS(bpf_prog_load_opts, opts, .expected_attach_type = BPF_TRACE_RAW_TP);
    struct bpf_insn insns[] = {
        { .code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = BPF_REG_0, .imm = 0 },
        { .code = BPF_JMP | BPF_EXIT },
    };
    int fd, insn_cnt = sizeof(insns) / sizeof(struct bpf_insn);

    opts.attach_btf_id = libbpf_find_vmlinux_btf_id(name, BPF_TRACE_RAW_TP);
    fd = bpf_prog_load(BPF_PROG_TYPE_TRACING, NULL, "GPL", insns, insn_cnt, &opts);
    if (fd >= 0)
        close(fd);
    return fd >= 0;
}

bool probe_ringbuf()
{
    int map_fd;

    map_fd = bpf_map_create(BPF_MAP_TYPE_RINGBUF, NULL, 0, 0, getpagesize(), NULL);
    if (map_fd < 0)
        return false;

    close(map_fd);
    return true;
}

int split_convert(char *s, const char* delim, void *elems, size_t elems_size,
          size_t elem_size, convert_fn_t convert)
{
    char *token;
    int ret;
    char *pos = (char *)elems;

    if (!s || !delim || !elems)
        return -EINVAL;

    token = strtok(s, delim);
    while (token) {
        if (pos + elem_size > (char*)elems + elems_size)
            return -ENOBUFS;

        ret = convert(token, pos);
        if (ret)
            return -ret;

        pos += elem_size;
        token = strtok(NULL, delim);
    }

    return 0;
}

int str_to_int(const char *src, void *dest)
{
    errno = 0;
    *(int*)dest = strtol(src, NULL, 10);

    return errno;
}

int str_to_long(const char *src, void *dest)
{
    errno = 0;
    *(long*)dest = strtol(src, NULL, 10);

    return errno;
}
